﻿using System;
using System.Collections.Generic;

namespace SharpTag.ID3
{
    /// <summary>
    /// Concreate implementation of the <see cref="IFrame"/> interface.
    /// </summary>
    public class ID3V2TagFrame : IFrame
    {
        private IFrameDefinition m_frameDefinition;

        /// <summary>
        /// Class constructor.
        /// </summary>
        public ID3V2TagFrame()
        {
            Data = new List<KeyValuePair<string, object>>();
            IsDirty = false;
        }

        /// <summary>
        /// Gets the <see cref="IFrameHeader"/> of the Frame.
        /// </summary>
        public IFrameHeader Header { get; private set; }

        /// <summary>
        /// Gets the Data fields found in the Frame.
        /// </summary>
        public List<KeyValuePair<string, object>> Data { get; private set; }

        /// <summary>
        /// Gets whether or not the Frame has been modified.
        /// </summary>
        public bool IsDirty { get; private set; }

        /// <summary>
        /// Adds a data field to the Frame.
        /// </summary>
        /// <param name="keyName">Name of the data field.</param>
        /// <param name="data">Value of the data field.</param>
        public void AddDataField(string keyName, object data)
        {
            var field = new KeyValuePair<string, object>(keyName, data);
            if (!Data.Contains(field))
            {
                Data.Add(field);
                IsDirty = true;
            }
                
        }

        /// <summary>
        /// Removes a data field from the Frame.
        /// </summary>
        /// <param name="fieldName">Name of the field to remove.</param>
        public void RemoveDataField(string fieldName)
        {
            foreach (var field in Data)
            {
                if (field.Key == fieldName)
                {
                    Data.Remove(field);
                    IsDirty = true;
                    break;
                }
            }
        }

        /// <summary>
        /// Sets the value of a data field in the Frame.
        /// </summary>
        /// <param name="fieldName">Name of the data field to change.</param>
        /// <param name="fieldValue">Value to assign to the data field.</param>
        public void SetDataField(string fieldName, object fieldValue)
        {
            foreach (var field in Data)
            {
                if (field.Key == fieldName)
                {
                    Data.Remove(field);
                    IsDirty = true;
                    break;
                }
            }

            Data.Add(new KeyValuePair<string, object>(fieldName, fieldValue));
        }

        /// <summary>
        /// Parses the raw frame data into the appropriate properties.
        /// </summary>
        /// <param name="frameBytes">A byte array containing the raw frame data.</param>
        public void ParseFrameData(byte[] frameBytes)
        {
            var headerBytes = new byte[10];
            Array.Copy(frameBytes, 0, headerBytes, 0, 10);
            Header = new ID3V2TagFrameHeader(headerBytes);
            m_frameDefinition = GetFrameDefinition();
            ParseFrameFieldData(frameBytes);
        }

        /// <summary>
        /// Uses the zlib.net library to compress the Frame data.
        /// </summary>
        public void Compress()
        {
            //TODO: Implement IFrame Compress method.
        }

        /// <summary>
        /// Uses the zlib.net library to uncompress the Frame data.
        /// </summary>
        public void Uncompress()
        {
            //TODO: Implement IFrame Uncompress method.
        }

        /// <summary>
        /// Converts the Frame Header data into a byte array to be written out to the physical file.
        /// </summary>
        /// <returns></returns>
        public byte[] PrepareBytes()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Helper method to parse the frame data.
        /// </summary>
        /// <param name="bytes">A byte array containing the raw frame data.</param>
        private void ParseFrameFieldData(byte[] bytes)
        {
            var count = 0;
            var frameDataSize = bytes.Length - 10;
            var frameBytes = new byte[frameDataSize];
            Array.Copy(bytes, 10, frameBytes, 0, frameDataSize);
            var byteArrays = frameBytes.ParseFrameBytes(m_frameDefinition);

            foreach (var def in m_frameDefinition.FrameDelimiters)
            {
                if (def.Value == FrameDelimiter.ZeroedByteString || def.Value == FrameDelimiter.EndOfFrameString)
                {
                    var length = byteArrays[count].Length;
                    var charArray = new char[length];
                    Array.Copy(byteArrays[count], 0, charArray, 0, length);
                    AddDataField(def.Key, new string(charArray));
                    count++;
                    continue;
                }

                AddDataField(def.Key, byteArrays[count]);
                count++;
            }
        }

        /// <summary>
        /// Gets the <see cref="IFrameDefinition"/> for the associated Frame.
        /// </summary>
        /// <returns>An instance of the <see cref="IFrameDefinition"/> interface.</returns>
        private IFrameDefinition GetFrameDefinition()
        {
            if (Header.Name.StartsWith("T"))
                return new TextFrameDefinition();
            if (Header.Name.StartsWith("W"))
                return new URLFrameDefinitions();

            switch (Header.Name)
            {
                case "AENC":
                    return new AENCFrameDefinition();
                case "APIC":
                    return new APICFrameDefinition();
                case "ASPI":
                    return new ASPIFrameDefinition();
                case "COMM":
                    return new COMMFrameDefinition();
                case "COMR":
                    return new COMRFrameDefinition();
                case "ENCR":
                    return new ENCRFrameDefinition();
                case "EQU2":
                    return new EQU2FrameDefinition();
                case "ETCO":
                    return new ETCOFrameDefinition();
                case "GEOB":
                    return new GEOBFrameDefinition();
                case "GRID":
                    return new GEOBFrameDefinition();
                case "LINK":
                    return new LINKFrameDefinition();
                case "MCDI":
                    return new MCDIFrameDefinition();
                case "MLLT":
                    return new MLLTFrameDefinition();
                case "OWNE":
                    return new OWNEFrameDefinition();
                case "PRIV":
                    return new PRIVFrameDefinition();
                case "PCNT":
                    return new PCNTFrameDefinition();
                case "POPM":
                    return new POPMFrameDefinition();
                case "POSS":
                    return new POSSFrameDefinition();
                case "RBUF":
                    return new RBUFFrameDefinition();
                case "RVA2":
                    return new RVA2FrameDefinition();
                case "RVRB":
                    return new RVRBFrameDefinition();
                case "SEEK":
                    return new SEEKFrameDefinition();
                case "SIGN":
                    return new SIGNFrameDefinition();
                case "SYLT":
                    return new SYLTFrameDefinition();
                case "SYTC":
                    return new SYTCFrameDefinition();
                case "UFID":
                    return new UFIDFrameDefinition();
                case "USER":
                    return new USERFrameDefinition();
                case "USLT":
                    return new USLTFrameDefinition();
                default:
                    return null;
            }
        }

    }
}
